#!/usr/bin/env python

import sys
from PyQt4 import QtCore, QtGui
from PassportDIY_UI import Ui_MainWindow
from PhotoFrame import FrameInfo
from PhotoConfig import PhotoConfig,  PhotoSize
from ConfigLoader import ConfigLoader
import os.path
import os
import math

class PhotoWindow(QtGui.QMainWindow):
    """
    Set up the GUI window for passport photo edit.
    Handle various type of action: mouse drag & drop, mouse wheel,
    button press, file open, file save and etc.
    """
    def __init__(self, parent=None):
        """Initial the GUI window and some global parameters"""
        QtGui.QWidget.__init__(self, parent)
        self.roundint = lambda x: math.trunc(round(x, 0))
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.cwd = os.getcwd()                      # current working directory
        self.ui.centralwidget.wheelEvent = self.OnWheelScroll
        self.img = ""
        self.pix = None                             # 
        self.img_w = 0                              # image file width
        self.img_h = 0                              # image file height
        self.view_x = 0                             # start x for view area on the image
        self.view_y = 0                             # start y for view area on the image
        self.ratio = 50                             # initial image zoom ratio in percent
        self.min_ratio = 0
        self.VIEW_MAX_WIDTH  = self.ui.photoView.maximumViewportSize().width()
        self.VIEW_MAX_HEIGHT = self.ui.photoView.maximumViewportSize().height()
        self.crop_rect = QtCore.QRect(0,0,\
                            self.VIEW_MAX_WIDTH,\
                            self.VIEW_MAX_HEIGHT)   # image crop rectangle information
        self.v_pos = QtCore.QPoint(0,0)
        self.frm = FrameInfo()
        self.defineFrame()                          # define the frame information
        self.doSignalConnection()                   # setup the signal and slot connection
        
    def doSignalConnection(self):
        """Setup signal and slot connections"""
        QtCore.QObject.connect(self.ui.menuOpen, QtCore.SIGNAL("triggered()"), self.OnMenuOpen)
        QtCore.QObject.connect(self.ui.menuExit, QtCore.SIGNAL("triggered()"), self.close)
        QtCore.QObject.connect(self.ui.menuAbout, QtCore.SIGNAL("triggered()"), self.OnMenuAbout)
        QtCore.QObject.connect(self.ui.btnMoveUp, QtCore.SIGNAL("clicked()"), self.OnButtonUp)
        QtCore.QObject.connect(self.ui.btnMoveDown, QtCore.SIGNAL("clicked()"), self.OnButtonDown)
        QtCore.QObject.connect(self.ui.btnMoveLeft, QtCore.SIGNAL("clicked()"), self.OnButtonLeft)
        QtCore.QObject.connect(self.ui.btnMoveRight, QtCore.SIGNAL("clicked()"), self.OnButtonRight)
        QtCore.QObject.connect(self.ui.btnZoomIn, QtCore.SIGNAL("clicked()"), self.OnButtonZoomIn)
        QtCore.QObject.connect(self.ui.btnZoomOut, QtCore.SIGNAL("clicked()"), self.OnButtonZoomOut)
        QtCore.QObject.connect(self.ui.btnSaveOne, QtCore.SIGNAL("clicked()"), self.OnButtonSaveOne)
        QtCore.QObject.connect(self.ui.btnSave4R, QtCore.SIGNAL("clicked()"), self.OnButtonSave4R)
        QtCore.QObject.connect(self.ui.zoomSlider, QtCore.SIGNAL("valueChanged(int)"), self.OnSliderChange)
        QtCore.QObject.connect(self.ui.zoomValue, QtCore.SIGNAL("valueChanged(int)"), self.OnSpinBoxChange)
        QtCore.QObject.connect(self.ui.photoView, QtCore.SIGNAL("photoMoved(PyQt_PyObject)"), self.OnPhotoMoved)
        QtCore.QObject.connect(self.ui.photoView, QtCore.SIGNAL("photoDropped(PyQt_PyObject)"), self.OnPhotoDropped)
        QtCore.QObject.connect(self.ui.listCountry, QtCore.SIGNAL("currentIndexChanged (int)"), self.OnCountryChange)
        QtCore.QObject.connect(self.ui.listPhotoType, QtCore.SIGNAL("currentIndexChanged (int)"), self.OnTypeChange)

    def OnMenuAbout(self):
        """Display the 'About' dialog message box"""
        QtGui.QMessageBox.about(self, "About PassportDIY",
                "<b>PassportDIY</b><br/><br/> "
                "Author: Zhu Yong<br/> "
                "Version: V 0.1")

    def OnInitRectPos(self):
        """Set the crop rectangle and display position upon file open event"""
        if self.img_w <= self.VIEW_MAX_WIDTH and self.img_h <= self.VIEW_MAX_HEIGHT:
            self.ratio = 100
            self.crop_rect = QtCore.QRect(0, 0, self.VIEW_MAX_WIDTH,  self.VIEW_MAX_HEIGHT)
            self.v_pos = QtCore.QPoint(self.VIEW_MAX_WIDTH - self.img_w,  self.VIEW_MAX_HEIGHT - self.img_h )/2
        elif self.img_w > self.VIEW_MAX_WIDTH and self.img_h <= self.VIEW_MAX_HEIGHT:
            self.ratio = 100
            self.crop_rect =QtCore.QRect((self.img_w - self.VIEW_MAX_WIDTH)/2,  0, self.VIEW_MAX_WIDTH,  self.VIEW_MAX_HEIGHT)
            self.v_pos = QtCore.QPoint(0,  (self.VIEW_MAX_HEIGHT - self.img_h )/2) 
        elif self.img_w <= self.VIEW_MAX_WIDTH and self.img_h > self.VIEW_MAX_HEIGHT:
            self.ratio = 100
            self.crop_rect =QtCore.QRect(0, (self.img_h - self.VIEW_MAX_HEIGHT)/2,  self.VIEW_MAX_WIDTH,  self.VIEW_MAX_HEIGHT)
            self.v_pos = QtCore.QPoint((self.VIEW_MAX_WIDTH - self.img_w )/2,  0) 
        else:
            if self.VIEW_MAX_WIDTH*100.0/self.img_w <= self.VIEW_MAX_HEIGHT*100.0/self.img_h:
                self.ratio = self.roundint(self.VIEW_MAX_HEIGHT*100.0/self.img_h)
                self.crop_rect =QtCore.QRect((self.roundint(self.img_w*self.ratio/100.0) - self.VIEW_MAX_WIDTH)/2,  0, self.VIEW_MAX_WIDTH,  self.VIEW_MAX_HEIGHT)
            else:
                self.ratio = self.roundint(self.VIEW_MAX_WIDTH*100.0/self.img_w,0)
                self.crop_rect =QtCore.QRect(0, (self.roundint(self.img_h*self.ratio/100.0) - self.VIEW_MAX_HEIGHT)/2,  self.VIEW_MAX_WIDTH,  self.VIEW_MAX_HEIGHT)
            self.v_pos = QtCore.QPoint(0, 0)     
        
    def LoadPhotoFile(self,  file):
        """Open Image file, either from File menu, or drop from window explorer"""
        try:
            self.img = file
            self.pix = QtGui.QPixmap(self.img)          # 
            self.img_w = self.pix.width()               # image file width
            self.img_h = self.pix.height()              # image file height
            self.w_size = self.roundint(self.img_w * self.ratio / 100.0) # picture width after zoom
            self.h_size = self.roundint(self.img_h * self.ratio / 100.0) # picture height after zoom
            w_ratio = self.roundint(self.frm.getSize().width()*100.0/self.img_w)
            h_ratio = self.roundint(self.frm.getSize().height()*100.0/self.img_h)
            if w_ratio>h_ratio:
                min_ratio = w_ratio
            else:
                min_ratio = h_ratio
            self.min_ratio = min_ratio
            self.ui.zoomSlider.setMinimum(min_ratio)
            self.ui.zoomValue.setMinimum(min_ratio)
            self.OnInitRectPos()            
            self.updateSlider()
            self.updateSpinBox()
            self.showImage()
        except:
            self.img = ""
            self.pix = None
            QtGui.QMessageBox.critical(self, "File open fail!",
                "Can't open image file", QtGui.QMessageBox.Ok)        
        
    def OnMenuOpen(self):
        """Slot for File Open menu"""
        i_file = QtGui.QFileDialog.getOpenFileName(self, caption="Select Image File", \
                                                   directory=self.cwd, \
                                                   filter = "*.jpg\n*.png\n*.bmp")
        if i_file.isEmpty():
            QtGui.QMessageBox.critical(self, "File open fail!",
                "Image file name is empty ", QtGui.QMessageBox.Ok)
            pass
        self.LoadPhotoFile(i_file) 

    
    def OnButtonUp(self):
        """
        Slot for Move Up button
        Move the displayed image up by 1 pix
        """
        if self.pix != None:
            self.updateRect(QtCore.QPoint(0, -1))
            self.showImage()

    def OnButtonDown(self):
        """
        Slot for Move Down button
        Move the displayed image down by 1 pix
        """
        if self.pix != None:
            self.updateRect(QtCore.QPoint(0, 1))
            self.showImage()

    def OnButtonLeft(self):
        """
        Slot for Move Left button
        Move the displayed image left by 1 pix
        """
        if self.pix != None:
            self.updateRect(QtCore.QPoint(-1, 0))
            self.showImage()

    def OnButtonRight(self):
        """
        Slot for Move Right button
        Move the displayed image right by 1 pix
        """        
        if self.pix != None:
            self.updateRect(QtCore.QPoint(1, 0))
            self.showImage()

    def OnButtonZoomIn(self):
        """
        Slot for Zoom In button
        Increase the zoom ratio by 1%
        """        
        if self.ratio < 100 and self.pix != None:
            self.OnRatioChange(1)
            self.updateSpinBox()
            self.updateSlider()
            self.showImage()

    def OnButtonZoomOut(self):
        """
        Slot for Zoom Out button
        Decrease the zoom ratio by 1%
        """         
        if self.ratio > self.min_ratio and self.pix != None:
            if self.ratio -1 >= self.min_ratio:
                self.OnRatioChange(-1)
                self.updateSpinBox()
                self.updateSlider()
                self.showImage()

    def OnButtonSaveOne(self):
        """ 
        Slot for Save One button
        Do scale first, then crop to target size
        """
        if self.pix == None:
            return
            
        o_file = QtGui.QFileDialog.getSaveFileName(self, caption="Select File to Save", \
                                                   directory=self.cwd, \
                                                   filter = "*.jpg\n*.png\n*.bmp")
        if not o_file.isEmpty():
            r_ratio = self.frm.getTargetSize().width()*1.0 / self.frm.getSize().width()
            r_pos = (self.crop_rect.topLeft() + self.frm.getPos())*r_ratio
            r_size = self.frm.getTargetSize()
            r_rect = QtCore.QRect(r_pos, r_size)
            o_pix = QtGui.QPixmap(self.img).scaled(self.w_size*r_ratio, self.w_size*r_ratio, 
                                        QtCore.Qt.KeepAspectRatio, 
                                        QtCore.Qt.SmoothTransformation).copy(r_rect)
            o_format = {'jpg':'JPG', 'jpeg':'JPG','png':'PNG','bmp':'BMP',}
            (shortname, extension) = os.path.splitext(str(o_file).lower())
            extension = extension.replace('.',  '')
            if extension in o_format.keys():
                o_pix.save(o_file, o_format[extension], 100)
                QtGui.QMessageBox.information (self, 'File Saved', \
                                    "Image file has been saved", QtGui.QMessageBox.Ok)
            else:
                QtGui.QMessageBox.critical (self, 'File Type Wrong', \
                                    "Output image type must be <b>JPEG</b>,<b>PNG</b>, or <b>BMP</b>", QtGui.QMessageBox.Ok)
        
    def OnButtonSave4R(self):
        """ 
        Slot for Save 4R button
        Do scale first, then crop to target size
        """        
        pass
            
    def OnSliderChange(self):
        """ 
        Slot for Slider value changed 
        Change the zoom ratio using slider
        """        
        if self.pix != None:
            steps = self.ui.zoomSlider.value() - self.ratio
            if steps !=0:
                self.OnRatioChange(steps)
                self.updateSpinBox()
                self.showImage()

    def OnSpinBoxChange(self):
        """ 
        Slot for Spinbox value changed 
        Change the zoom ratio using spinbox
        """           
        if self.pix != None:
            steps = self.ui.zoomValue.value() - self.ratio
            if steps != 0:
                self.OnRatioChange(steps)
                self.updateSlider()
                self.showImage()

    def updateSlider(self):
        """Update the slider value"""
        self.ui.zoomSlider.setValue(self.ratio)
        
    def updateSpinBox(self):
        """Update the spinbox value"""
        self.ui.zoomValue.setValue(self.ratio)

    def OnPhotoMoved(self, point):
        """
        Slot for mouse move photo event
        """
        if self.pix != None:
            self.updateRect(point)
            self.showImage()

    def OnPhotoDropped(self,  file):
        """
        Slot for mouse drop photo file into photo view area
        """
        self.LoadPhotoFile(file) 

    def updateRect(self, offset):
        """
        Change crop rectangle and display position information according to
        current zoom ratio and image size
        """
        if self.v_pos.x() == 0:
            c_left = self.crop_rect.left() - offset.x()
        elif self.v_pos.x() > 0:
            c_left = self.crop_rect.left() - offset.x() - self.v_pos.x()
        
        if self.v_pos.y() == 0:
            c_top = self.crop_rect.top() - offset.y()
        elif self.v_pos.y() >0:
            c_top = self.crop_rect.top() - offset.y()  - self.v_pos.y()
            
        if c_left < 0:
            if abs(c_left) <= self.frm.getPos().x():
                if abs(c_left) + self.w_size < self.frm.getPos().x() + self.frm.getSize().width():
                    c_left = self.frm.getPos().x() + self.frm.getSize().width() - self.w_size
                self.v_pos.setX(abs(c_left))
            else:
                self.v_pos.setX(self.frm.getPos().x())
            c_left = 0
        else:
            self.v_pos.setX(0)
            min_right = self.VIEW_MAX_WIDTH - self.frm.getPos().x() - self.frm.getSize().width()
            c_right = self.VIEW_MAX_WIDTH -(self.w_size - c_left)
            if c_right >  min_right:
                c_left = min_right + self.w_size - self.VIEW_MAX_WIDTH        

        if c_top < 0:
            if abs(c_top) < self.frm.getPos().y():
                if abs(c_top) + self.h_size < self.frm.getPos().y() + self.frm.getSize().height():
                    c_top = self.frm.getPos().y() + self.frm.getSize().height() - self.h_size
                self.v_pos.setY(abs(c_top))
            else:
                self.v_pos.setY(self.frm.getPos().y())
            c_top = 0
        else:
            self.v_pos.setY(0)
            min_bottom = self.VIEW_MAX_HEIGHT - self.frm.getPos().y() - self.frm.getSize().height()
            c_bottom = self.VIEW_MAX_HEIGHT -(self.h_size - c_top)
            if c_bottom >  min_bottom:
                c_top = min_bottom + self.h_size - self.VIEW_MAX_HEIGHT
        
        self.crop_rect.setLeft(c_left)
        self.crop_rect.setTop(c_top)
        self.crop_rect.setWidth(self.VIEW_MAX_WIDTH)
        self.crop_rect.setHeight(self.VIEW_MAX_HEIGHT)
        
    def defineFrame(self):
        """
        Initial the photo crop frame information according to photo type selected
        """
        idx = self.ui.listPhotoType.currentIndex()
        self.cfg = self.ui.cfg_list[idx]
        
        frm_w = 200
        frm_h = self.roundint(self.cfg.size.height()*frm_w*1.0/self.cfg.size.width())
        self.frm.setSize(frm_w, frm_h)      # set crop fame size
        
        left = (self.VIEW_MAX_WIDTH - frm_w)/2
        top = (self.VIEW_MAX_HEIGHT - frm_h)/2
        self.frm.setPos(left, top)          # set crop frame position
        
        offset = self.roundint(frm_h*5/100.0)
        self.frm.setH1Pos(frm_w/4, offset)  # set head 2 chin top line
        
        top = self.roundint(self.cfg.h2c_min*frm_h*1.0/self.cfg.size.height()) + offset
        self.frm.setH2Pos(frm_w/4, top)     # set head 2 chin min line
        
        top = self.roundint(self.cfg.h2c_max*frm_h*1.0/self.cfg.size.height()) + offset
        self.frm.setH3Pos(frm_w/4, top)     # set head 2 chin max line
        
        self.frm.setVPos(frm_w/2, offset)   # set center vertical line
        
        if self.cfg.unit == self.cfg.UNIT_PIX:
            w = self.cfg.size.width()
            h = self.cfg.size.height()
        elif self.cfg.unit == self.cfg.UNIT_INCH:
            w = self.cfg.size.width()*self.cfg.dpi
            h = self.cfg.size.height()*self.cfg.dpi
        elif self.cfg.unit == self.cfg.UNIT_CM:
            w = self.roundint(self.cfg.size.width()*self.cfg.dpi*0.3937)
            h = self.roundint(self.cfg.size.height()*self.cfg.dpi*0.3937)
        self.frm.setTargetSize(w, h)   
            
        
     
    def drawPhoto(self, c_view, p_view):
        """ Draw the photo onto viewable area which located at center"""
        paint = QtGui.QPainter()
        paint.begin(p_view)
        paint.drawPixmap(self.v_pos, c_view)
        paint.end()
        
    def drawPhotoFrame(self, p_view):
        """ Draw the passport crop assistant frame onto the viewable picture"""
        paint = QtGui.QPainter()
        paint.begin(p_view)
        paint.setPen(QtGui.QPen(QtCore.Qt.blue, 1, QtCore.Qt.SolidLine))
        paint.drawRect(self.frm.getFrameRect())
        paint.drawLine(self.frm.getH1Line())
        paint.drawLine(self.frm.getH2Line())
        paint.drawLine(self.frm.getH3Line())
        paint.drawLine(self.frm.getVLine())
        paint.setPen(QtCore.Qt.NoPen)
        paint.setBrush(QtGui.QBrush(QtGui.QColor(139,134,130,150),QtCore.Qt.SolidPattern))
        paint.drawRect(0, 
                       0, 
                       self.VIEW_MAX_WIDTH, 
                       self.frm.getPos().y())
        paint.drawRect(0, 
                       self.frm.getPos().y(), 
                       self.frm.getPos().x(), 
                       self.frm.getSize().height())
        paint.drawRect(self.frm.getPos().x() + self.frm.getSize().width(), 
                       self.frm.getPos().y(),
                       self.VIEW_MAX_WIDTH - self.frm.getPos().x() - self.frm.getSize().width(),
                       self.frm.getSize().height())
        paint.drawRect(0, 
                       self.frm.getPos().y() + self.frm.getSize().height(),
                       self.VIEW_MAX_WIDTH,
                       self.VIEW_MAX_HEIGHT - self.frm.getPos().y() - self.frm.getSize().height())
                       
        paint.end()        
        
    def showImage(self):
        """Display the photo in photo view area"""
        if self.pix != None:
            self.w_size = self.roundint(self.img_w * self.ratio / 100.0) # picture width after zoom
            self.h_size = self.roundint(self.img_h * self.ratio / 100.0) # picture height after zoom
            c_view = self.pix.scaled(self.w_size, self.h_size,          # copy picture for viewable area
                                    QtCore.Qt.KeepAspectRatio, 
                                    QtCore.Qt.FastTransformation).copy(self.crop_rect)           
            p_view = QtGui.QPixmap(self.VIEW_MAX_WIDTH, self.VIEW_MAX_HEIGHT)
            p_view.fill(QtGui.QColor(0,0,100,255))
            self.drawPhoto(c_view, p_view)                              # draw photo in viewable area
            self.drawPhotoFrame(p_view)                                 # draw the assistant frame
            self.ui.photoScene.clear()
            self.ui.photoScene.setSceneRect(0, 0, 
                                            self.VIEW_MAX_WIDTH, 
                                            self.VIEW_MAX_HEIGHT)       # set viewable area size
            self.ui.photoScene.addPixmap(p_view)
            QtCore.QCoreApplication.processEvents()

    def OnRatioChange(self,  steps):
        """
        Check the target zoom ration against minimum zoom ratio
        Also update the crop rectangle and display position according to zoom ratio
        """
        new_ratio = self.ratio + steps
        
        if new_ratio < self.min_ratio:
            new_ratio = self.min_ratio

        off_ratio = new_ratio - self.ratio                    # offset ratio
        self.ratio = new_ratio
        off_x = self.roundint((self.VIEW_MAX_WIDTH/2 - self.v_pos.x())/100.0*off_ratio)
        off_y = self.roundint((self.VIEW_MAX_HEIGHT/2 - self.v_pos.y())/100.0*off_ratio)
        
        self.updateRect(QtCore.QPoint(-off_x, -off_y))

    def OnWheelScroll(self, event):
        """
        Slot for mouse wheel scroll event in the photo view area
        Mouse wheel to change the zoom ratio
        """
        if self.pix != None:
            degree = event.delta() / 8.0
            steps = self.roundint(degree / 15.0)
            self.OnRatioChange(steps)
            self.showImage()
            self.updateSlider()
            self.updateSpinBox()
            event.accept()

    def OnCountryChange(self):
        cfg_dir = self.cwd + "/config/"
        file = cfg_dir + str(self.ui.listCountry.currentText()) + ".xml"
        self.ui.cfg_list = ConfigLoader(file).GetConfigs()
        self.ui.listPhotoType.clear()
        for cfg in self.ui.cfg_list:
            self.ui.listPhotoType.addItem(cfg.name)
        self.ui.listPhotoType.setCurrentIndex(0)
        self.ui.updateLabel()
        
    def OnTypeChange(self):
        self.defineFrame()
        self.ui.updateLabel()
        self.showImage()
            
if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    myapp = PhotoWindow()
    myapp.show()
    sys.exit(app.exec_())
